/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package game.experts.combine;

import analysys.HECAlignmentHit;
import game.datahandling.dataenvelopes.base.PrimaryStructure;
import game.encodings.OutputEncoding;
import game.experts.Expert;
import java.util.ArrayList;
import java.util.List;
import utils.Profile;
import utils.ProfileMatrix;

/**
 *
 * @author emanuele
 */
public class SSPWeightedWindowHintedCombinerExpert extends SSPBaseHintedCombinerExpert {

    public int maximumWindowNumber = 10;
    public int minimumWindowSize = 10;

    private int windowNumber;
    private int windowSize;

    protected List<String> alignHintPredictions(Expert<PrimaryStructure> e) {
        List<String> ret = new ArrayList<String>();

        for (HECAlignmentHit hint: this.current.hints) {
            // phws = Prediction of Hit Whole Secondary
            String phws = e.predict(new PrimaryStructure(hint.hitWholePri)).decode().toString();
            // phas = Prediction of Hit Aligned Secondary
            StringBuffer phas = new StringBuffer("");
            int j = hint.hitAlignmentStartIndex;
            for (int i = 0; i < hint.hitAlignedSec.length(); i++) {
                if (hint.hitAlignedSec.charAt(i) == '-') {
                    phas.append('-');
                } else {
                    phas.append(phws.charAt(j));
                    j++;
                }
            }
            ret.add(phas.toString());
        }

        return ret;
    }

    protected List<Double> getExpertWeights(Expert<PrimaryStructure> e) {
        List<Double> weights = new ArrayList<Double>();

        if (this.current.hints.isEmpty()) {
            // Non c'è allineamento: tutti i pesi sono uguali.
            for (int i = 0; i < this.windowNumber; i++) {
                weights.add(1.0);
            }
            return weights;
        }

        // Hit Aligned Predictions
        List<String> haps = this.alignHintPredictions(e);

        for (int hintIdx = 0; hintIdx < this.current.hints.size(); hintIdx++) {
            String hap = haps.get(hintIdx);
            // Per ogni predizione (allineata) dell'hit (Hit Aligned Prediction):

            // Prendi la struttura secondaria vera (Hit Aligned Secondary)
            String has = this.current.hints.get(hintIdx).hitAlignedSec;

            for (int win = 0; win < this.windowNumber; win++) {
                // Per ogni finestra nella predizione:

                int startIdx, endIdx;
                double weight;

                startIdx = win*this.windowSize;
                if (win + 1 != this.windowNumber) {
                    // Prendiamo il punto finale della finestra
                    endIdx = (win+1)*this.windowSize;
                } else {
                    // Il punto finale è la fine della stringa
                    endIdx = hap.length();
                }

                // Real length toglie i gap.
                int realLength = 0;

                for (int i = startIdx; i < endIdx; i++) {
                    if (hap.charAt(i) == '-')
                        continue;
                    realLength++;
                }

                if (realLength == 0) {
                    // La finestra contiene solo '-', quindi il peso è uno.
                    weight = 1.0;
                } else {
                    double errors = this.getDifferences(hap, has, startIdx, endIdx);
                    errors += 0.5; // Per evitare errorRate == 0

                    double errorRate = errors/realLength;

                    weight = this.weightFromErrorRate(errorRate);
                }

                // Se siamo al primo hint inserisci il peso, altrimenti aggiornalo
                if (hintIdx == 0) {
                    weights.add(weight);
                } else {
                    double prevWeight = weights.get(win);
                    weights.set(win, prevWeight + weight);
                }
            }
        }

        return weights;
    }

    @Override
    protected Profile mergePredictions(OutputEncoding[] allPredictions) {
        Profile ret = new ProfileMatrix(3, this.current.input.length(), 0);

        // Calcolo windowSize: se è minore di minimumWindowSize, cambio windowNumber
        this.windowNumber = this.maximumWindowNumber;
        this.windowSize = this.current.input.length() / this.windowNumber;
        if (this.windowSize < this.minimumWindowSize) {
            this.windowSize = this.minimumWindowSize;
            this.windowNumber = this.current.input.length() / this.windowSize;
        }

        for (int i = 0; i < allPredictions.length; i++) {
            Profile prediction = allPredictions[i].getProfile();
            List<Double> expertWeights = this.getExpertWeights(this.experts.get(i));

            // Per ogni colonna della predizione: prendi la colonna, prendi il peso associato
            // alla colonna (che apparterrà ad una delle finestre), moltiplica la colonna per
            // il peso e setta la colonna pesata.
            for (int col = 0; col < prediction.length(); col++) {
                double[] colValues = prediction.getColumn(col);

                int currWindow = col / this.windowSize;
                if (currWindow >= this.windowNumber) // L'ultima finestra è più lunga perché ha il resto dei residui
                    currWindow = this.windowNumber - 1;
                double currWeight = expertWeights.get(currWindow);

                for (int j = 0; j < colValues.length; j++)
                    colValues[j] *= currWeight;

                prediction.setColumn(col, colValues);
            }

            ret.add(prediction);
        }
        ret.normalizeColumns();

        return ret;
    }

}
